package com.hc.utils;

import java.time.LocalDate;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 410001910101123  410001 910101 123
 * 410122198410108352
 * 41000119910101123X  410001 19910101 123X
 * 15位：6位地址码+6位出生年月日（900101代表1990年1月1日出生）+3位顺序码
 * 18位：6位地址码+8位出生年月日（年、月、日分别用4位、2位（不足两位加0）、2位（不足两位加0）数字表示，之间不用分隔符）+3位顺序码+1位校验码
 * 地区码：
 * 1、 第一、二位表示省（自治区、直辖市、特别行政区）。
 * 2、 第三、四位表示市（地级市、自治州、盟及国家直辖市所属市辖区和县的汇总码）。其中，01-20，51-70表示省直辖市；21-50表示地区（自治州、盟）。
 * 3、 第五、六位表示县（市辖区、县级市、旗）。01-18表示市辖区或地区（自治州、盟）辖县级市；21-80表示县（旗）；81-99表示省直辖县级市。
 * 顺序码：表示在同一地址码所标识的区域范围内，对同年、同月、同日出生的人编定的顺序号，顺序码的奇数分配给男性，偶数分配给女性。
 * 校验码：是由号码编制单位按统一的公式计算出来的，如果某人的尾号是0-9，都不会出现X，但如果尾号是10，那么就得用X来代替，因为如果用10做尾号，那么此人的身份证就变成了19位，
 * 而19位的号码违反了国家标准，并且中国的计算机应用系统也不承认19位的身份证号码。Ⅹ是罗马数字的10，用X来代替10，可以保证公民的身份证符合国家标准。
 * 具体校验的计算方式及步骤如下：
 * 1）对前17位数字本体码加权求和：
 * S = Sum(Ai * Wi), i = 0, ... , 16
 * 　　其中Ai表示第i位置上的身份证号码数字值，Wi表示第i位置上的加权因子，其各位对应的值依次为： 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
 * 2）以11对S取模：
 * Y = mod(S, 11)
 * 3）根据模Y 的值得到对应的校验码
 * Y值    0 1 2 3 4 5 6 7 8 9 10
 * 　　校验码 1 0 X 9 8 7 6 5 4 3 2
 * 其中X来代替用来罗马数字中的10，即Ⅹ。
 */
public class IdCardUtil {

    /**
     * 将15位转换为18位
     *
     * @param idCard 15位身份证号
     * @return String 18位身份证号
     */
    private static String tran15to18(String idCard) {
        if (idCard == null || "".equals(idCard)) {
            throw new RuntimeException("身份证不能为空");
        }
        if (idCard.length() != 15) {
            throw new RuntimeException("身份证长度不正确");
        }
        idCard = idCard.substring(0, 6) + "19" + idCard.substring(6);//加入年份，变成17位

        //计算校验位   begin
        char[] Ai = idCard.toCharArray();
        int[] Wi = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
        char[] verifyCode = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
        int S = 0;
        int Y;
        for (int i = 0; i < Wi.length; i++) {
            S += (Ai[i] - '0') * Wi[i];
        }
        Y = S % 11;
        //校验位计算结果verifyCode[Y] end

        idCard += verifyCode[Y];
        return idCard;
    }

    /**
     * 对身份证号的第18位进行校验
     *
     * @param idCard 410122198410108352
     * @return 符合规则返回true，否则返回false
     */
    private static boolean checkLastDigit(String idCard) {
        if (idCard == null || "".equals(idCard)) {
            throw new RuntimeException("身份证不能为空");
        }
        if (idCard.length() != 15 && idCard.length() != 18) {
            throw new RuntimeException("身份证长度不正确");
        }
        //将15位的身份证号转换成18位的
        if (idCard.length() == 15) {
            idCard = tran15to18(idCard);
        }
        char[] charArray = idCard.toCharArray();
        //前十七位加权因子
        int[] idCardWi = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
        //这是除以11后，可能产生的11位余数对应的验证码
        String[] idCardY = {"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};
        int sum = 0;
        for (int i = 0; i < idCardWi.length; i++) {
            int current = Integer.parseInt(String.valueOf(charArray[i]));
            int count = current * idCardWi[i];
            sum += count;
        }
        char idCardLast = charArray[17];
        int idCardMod = sum % 11;
        if (idCardY[idCardMod].toUpperCase().equals(String.valueOf(idCardLast).toUpperCase())) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 验证18身份证号中的日期部分是否为合法  yyyyMMdd
     *
     * @param idCard 410122198410108352
     * @return
     */
    private static boolean checkBirth(String idCard) {
        if (idCard == null || "".equals(idCard)) {
            throw new RuntimeException("身份证不能为空");
        }
        if (idCard.length() != 15 && idCard.length() != 18) {
            throw new RuntimeException("身份证长度不正确");
        }
        //将15位的身份证号转换成18位的
        if (idCard.length() == 15) {
            idCard = tran15to18(idCard);
        }
        Pattern p = Pattern.compile("([\\d]{4}(((0[13578]|1[02])((0[1-9])|([12][0-9])|(3[01])))|(((0[469])|11)((0[1-9])|([12][0-9])|30))|(02((0[1-9])|(1[0-9])|(2[0-8])))))|((((([02468][048])|([13579][26]))00)|([0-9]{2}(([02468][048])|([13579][26]))))(((0[13578]|1[02])((0[1-9])|([12][0-9])|(3[01])))|(((0[469])|11)((0[1-9])|([12][0-9])|30))|(02((0[1-9])|(1[0-9])|(2[0-9])))))");
        return p.matcher(idCard.substring(6, 14)).matches();
    }

    /**
     * 验证身份证号码
     * <p>
     * 正则表达式
     * 十八位： ^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$
     * 十五位： ^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$
     * 总： ( ^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)
     *
     * @param idCard 居民身份证号码15位或18位，最后一位可能是数字或字母
     * @return 验证成功返回位的身份证号，失败时抛出异常
     */
    public static String checkIdCard(String idCard) {
        //如果日期部分格式不正确，返回false
        if (!checkBirth(idCard)) {
            throw new RuntimeException("身份证信息不正确");
        }
        //将15位的身份证号转换成18位的
        if (idCard.length() == 15) {
            idCard = tran15to18(idCard);
        }
        // 定义判别用户身份证号的正则表达式（15位或者18位，最后一位可以为字母）
        String regularExpression = "^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$";

        boolean matches = idCard.matches(regularExpression);

        //判断第18位校验值
        if (matches && checkLastDigit(idCard)) {
            return idCard;
        } else {
            throw new RuntimeException("身份证信息不正确");
        }
    }

    /**
     * 获取身份证号码中的生日，格式：yyyy-MM-dd
     *
     * @param idCard
     * @return
     */
    public static String getBirth(String idCard) {
        idCard = checkIdCard(idCard);
        String tmp = idCard.substring(6, 14);
        return new StringBuffer(tmp).insert(4, "-").insert(7, "-").toString();
    }

    /**
     * 获取性别
     *
     * @param idCard
     * @return true：女 false：男
     */
    public static boolean getGender(String idCard) {
        idCard = checkIdCard(idCard);
        String tmp = idCard.substring(14, 17);
        return Integer.parseInt(tmp) % 2 == 0 ? true : false;
    }


    /**
     * 根据身份证号，获取年龄
     *
     * @param idCard
     * @return
     */
    public static int getAge(String idCard) {
        idCard = checkIdCard(idCard);
        String tmp = idCard.substring(6, 10);

        return LocalDate.now().get(ChronoField.YEAR) - Integer.parseInt(tmp);
    }

    /**
     * 解析身份证背面的地址，获取省市县乡村，并以List的形式返回
     *
     * @param addr
     * @return
     * @author lin
     */
    public static List<String> splitAddr(String addr) {
        String regex = "(?<province>[^省]+省|[^自治区]+自治区|.+市)(?<city>[^自治州]+自治州|.+区划|[^市]+市|[^盟]+盟|[^地区]+地区)?(?<county>[^市]+市|[^县]+县|[^旗]+旗|.+区)?(?<town>[^区]+区|[^乡]+乡|[^镇]+镇|.+镇)?(?<village>.*)";

        List<String> res = new ArrayList<>(5);
        Matcher m = Pattern.compile(regex).matcher(addr);
        while (m.find()) {
            String village = m.group("village");
            res.add(m.group("province"));
            res.add(m.group("city"));
            res.add(m.group("county"));
            res.add(m.group("town"));
            res.add(village);
        }
        return res;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(splitAddr("河南省郑州市中牟县万滩乡万滩村"));
        System.out.println(splitAddr("新疆维吾尔自治区塔城地区沙湾县xxxx镇yyyy付"));
        System.out.println(splitAddr("新疆维吾尔自治区自治区直辖县级行政区划五家渠市"));
        System.out.println(splitAddr("北京市朝阳区复兴门内大街143号"));
        System.out.println(splitAddr("湖北省武汉市洪山区"));
        System.out.println(splitAddr("湖北省恩施土家族苗族自治州恩施市"));
        System.out.println(splitAddr("山东省潍坊市青州市开发区向阳路143号"));
        System.out.println(splitAddr("西藏自治区日喀则地区日喀则市"));
        System.out.println(splitAddr("内蒙古自治区兴安盟科尔沁右翼前旗"));
        System.out.println(splitAddr("海南省省直辖县级行政区划临高县中沙群岛的岛礁及其海域"));
        System.out.println(splitAddr("北京市市辖区朝阳区"));

        String idCard1 = "410122198410108652";
        String idCard2 = "370725881105149";
        System.out.println(checkBirth(idCard1));
        System.out.println(checkBirth(idCard2));
        System.out.println(getBirth(idCard1));
        System.out.println(getBirth(idCard2));

        System.out.println(checkLastDigit(idCard1));
        System.out.println(checkLastDigit(idCard2));

        String x = tran15to18(idCard2);
        System.out.println(x);
        System.out.println(checkLastDigit(x));

        System.out.println(checkIdCard(idCard1));
        System.out.println(checkIdCard(x));

        System.out.println(getGender(idCard1));
        System.out.println(getGender(idCard2));

        System.out.println(getAge(idCard1));
        System.out.println(getAge(idCard2));
    }
}
